/**
 * JWT工具类
 * 
 * 功能说明：
 * - JWT令牌的生成、解析和验证
 * - 支持访问令牌和刷新令牌
 * - 支持用户ID、用户名、角色等信息的存储
 * - 提供令牌过期检查和用户权限提取
 * 
 * JWT结构：
 * Header（头部）：包含令牌类型和签名算法
 * Payload（负载）：包含用户信息、角色、过期时间等
 * Signature（签名）：用于验证令牌完整性
 * 
 * 使用算法：HMAC-SHA256（HS256）
 * 
 * @author eCommerce Team
 * @version 1.0
 */
package com.ecommerce.auth.util;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import jakarta.annotation.PostConstruct;
import javax.crypto.SecretKey;
import java.security.Key;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;

/**
 * JWT工具类
 * 负责JWT令牌的创建、解析和验证
 */
@Component
public class JwtUtil {

    /**
     * JWT密钥配置
     * 从application.yml中读取，用于令牌签名
     */
    @Value("${jwt.secret}")
    private String secret;

    /**
     * 访问令牌过期时间（毫秒）
     * 默认24小时，从配置文件中读取
     */
    @Value("${jwt.expiration}")
    private Long expiration;

    /**
     * 刷新令牌过期时间（毫秒）
     * 默认7天，从配置文件中读取
     */
    @Value("${jwt.refresh-expiration}")
    private Long refreshExpiration;

    /**
     * JWT签名密钥
     * 在初始化时根据secret生成，用于令牌签名和验证
     */
    private SecretKey key;

    /**
     * 初始化方法
     * 
     * 在Bean创建后执行，用于：
     * 1. 根据配置的secret生成HMAC-SHA256签名密钥
     * 2. 确保密钥长度符合JWT规范要求（至少256位）
     * 
     * @PostConstruct注解确保此方法在依赖注入完成后自动执行
     */
    @PostConstruct
    public void init() {
        this.key = Keys.hmacShaKeyFor(secret.getBytes());
    }

    /**
     * 生成访问令牌（Access Token）
     * 
     * 令牌包含信息：
     * - 用户名（subject）
     * - 用户ID（自定义声明）
     * - 用户角色列表
     * - 用户类型（管理员、普通用户等）
     * - 主要角色（角色列表中的第一个角色）
     * 
     * 使用配置文件中定义的过期时间
     * 
     * @param username 用户名
     * @param userId 用户ID
     * @param roles 用户角色列表
     * @param userType 用户类型
     * @return 生成的JWT访问令牌
     */
    public String generateToken(String username, Long userId, List<String> roles, String userType) {
        Map<String, Object> claims = new HashMap<>();
        // 确保userId在token中以String类型存储，避免类型转换问题
        claims.put("userId", userId.toString());
        claims.put("roles", roles);
        claims.put("userType", userType);
        claims.put("role", roles != null && !roles.isEmpty() ? roles.get(0) : "USER");
        return createToken(claims, username, expiration);
    }

    public String generateRefreshToken(String username, Long userId) {
        Map<String, Object> claims = new HashMap<>();
        // 确保userId在token中以String类型存储，避免类型转换问题
        claims.put("userId", userId.toString());
        claims.put("refresh", true);
        return createToken(claims, username, refreshExpiration);
    }

    private String createToken(Map<String, Object> claims, String subject, Long expirationTime) {
        Date now = new Date();
        Date validity = new Date(now.getTime() + expirationTime);

        return Jwts.builder()
                .claims(claims)
                .subject(subject)
                .issuedAt(now)
                .expiration(validity)
                .signWith(key)
                .compact();
    }

    public String extractUsername(String token) {
        return extractClaim(token, Claims::getSubject);
    }

    public String extractUserId(String token) {
        Object userIdObj = extractClaim(token, claims -> claims.get("userId"));
        if (userIdObj == null) {
            return null;
        }
        // 统一将userId转换为String类型
        if (userIdObj instanceof Long) {
            return userIdObj.toString();
        } else if (userIdObj instanceof Integer) {
            return ((Integer) userIdObj).toString();
        } else if (userIdObj instanceof String) {
            String userIdStr = (String) userIdObj;
            return userIdStr != null ? userIdStr.trim() : null;
        }
        return null;
    }

    public String extractRole(String token) {
        return extractClaim(token, claims -> claims.get("role", String.class));
    }

    public Date extractExpiration(String token) {
        return extractClaim(token, Claims::getExpiration);
    }

    public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
        final Claims claims = extractAllClaims(token);
        return claimsResolver.apply(claims);
    }

    private Claims extractAllClaims(String token) {
        return Jwts.parser().verifyWith(key).build().parseSignedClaims(token).getPayload();
    }

    private Boolean isTokenExpired(String token) {
        return extractExpiration(token).before(new Date());
    }

    public Boolean isInvalid(String token) {
        try {
            extractAllClaims(token);
            return isTokenExpired(token);
        } catch (Exception e) {
            System.out.println("Token validation error: " + e.getMessage());
            return true;
        }
    }

    // 保持向后兼容的方法
    public Claims getAllClaimsFromToken(String token) {
        return extractAllClaims(token);
    }

    public String getUsername(String token) {
        return extractUsername(token);
    }

    public Long getUserId(String token) {
        String userIdStr = extractUserId(token);
        if (userIdStr != null && !userIdStr.trim().isEmpty()) {
            try {
                return Long.parseLong(userIdStr.trim());
            } catch (NumberFormatException e) {
                System.out.println("解析用户ID失败: " + e.getMessage());
                return null;
            }
        }
        return null;
    }

    public List<String> getRoles(String token) {
        return getAllClaimsFromToken(token).get("roles", List.class);
    }

    public String getUserType(String token) {
        return getAllClaimsFromToken(token).get("userType", String.class);
    }

    public boolean isRefreshToken(String token) {
        return Boolean.TRUE.equals(getAllClaimsFromToken(token).get("refresh", Boolean.class));
    }

    /**
     * 使令牌失效
     * 
     * 将令牌加入黑名单，防止重复使用
     * 注意：这是一个简化实现，实际项目中应该将黑名单存储在Redis等缓存中
     * 
     * @param token 要失效的JWT令牌
     */
    public void invalidateToken(String token) {
        // 在实际项目中，这里应该将令牌存储到Redis黑名单中
        // 当前实现仅作为占位符，依赖令牌的过期机制
        // TODO: 实现基于Redis的令牌黑名单机制
        System.out.println("Token invalidated: " + token.substring(0, Math.min(20, token.length())) + "...");
    }

    /**
     * 生成服务间调用的JWT令牌
     * 
     * 功能说明：
     * - 用于微服务间调用认证服务获取访问令牌
     * - 验证服务身份并返回有效的JWT令牌
     * - 支持服务间安全通信
     * 
     * 令牌包含信息：
     * - 服务ID（subject）
     * - 服务标识（自定义声明）
     * - 服务角色（固定为"SERVICE"）
     * 
     * 令牌有效期：
     * - 默认1小时（3600秒），适合服务间调用
     * - 比用户令牌有效期短，提高安全性
     * 
     * @param serviceId 服务ID，标识调用方的服务名称
     * @return 生成的服务JWT令牌
     */
    public String generateServiceToken(String serviceId) {
        Map<String, Object> claims = new HashMap<>();
        claims.put("service", true); // 标识为服务令牌
        claims.put("serviceId", serviceId); // 存储服务ID
        claims.put("role", "SERVICE"); // 设置服务角色
        
        // 服务令牌有效期为1小时（3600秒）
        long serviceTokenExpiration = 3600 * 1000; // 毫秒
        
        return createToken(claims, serviceId, serviceTokenExpiration);
    }
}